import uuid
import SocketServer
import random
from game import packer, Card, Command, PORT, auth, trim_msg
from socket import error as SocketError

def get_rand_card() :
	card_type = random.randint(1, 4)
	card_point = random.randint(1, 13)
	return Card(card_type, card_point)

class Player :
	def __init__(self, name="noname") :
		self.uid = str(uuid.uuid1())
		self.bet = 0
		self.cards = []
		self.name = name
		self.ready = False
		self.added = False

	def register_socket_handler(self, handler) :
		self.handler = handler

	def send(self, cmd, msg = "", card_type = 0, card_point = 0) :
		data = Command()
		raw_data = data.pack(cmd, str(msg), card_type, card_point)
		try:
			self.handler.sendall(raw_data)
		except SocketError as e:
			print '*** Failed to send message to the player %s.' % self.name
			print e

	def send_msg(self, msg) :
		self.send(Command.CMD_MSG, msg)

	def send_ack(self) :
		self.send(Command.CMD_ACK)		

	def add_card(self, card) :
		self.cards.append(card)
		self.send(Command.CMD_GIVE_CARD, "Server passes a card to you.", card.card_type, card.card_point)

	def is_busted(self) :
		if self.total_point() > 21 :
			return True
		return False
		
	def total_point(self) :
		total = 0
		sumAce = 0
		for idx in xrange(len(self.cards)) :
			if self.cards[idx].card_point == 1:
				sumAce = sumAce + 1

			if self.cards[idx].card_point > 10 : 
				total = total + 10
			else :
				total = total + self.cards[idx].card_point

		for idx in xrange(1, 10):
			if sumAce == 0 or total > 11:
				break;
			total = total + 10
			sumAce = sumAce - 1
				
		return total

	def acquire_random_card(self) :
		card = get_rand_card()
		self.add_card(card)
		return card		

class Dealer(Player) :
	def __init__(self):
		Player.__init__(self, "Dealer")
		self.ready = True
		self.bet = 1
		self.added = True

	def send(self, cmd, msg, card_type, card_point) :
		# print msg
		pass


class GameRoom() : 
	def __init__(self) :
		self._initialize()

	def _initialize(self) :
		self.players = dict()
		self.game_started = False
		self.dealer = Dealer()
		self.add_player(self.dealer)
		print "Game started. Waiting for new players..."

	def on_player_ready(self, player) :	
		if not all([p.ready for p in self.players.values()]):
			return
		
		for curuid in self.players :
			for curuid2 in self.players :
				if not curuid == curuid2 :
					self.players[curuid].send(Command.CMD_PLAYER_ENTER, self.players[curuid2].uid, 0, 0)
					self.players[curuid].send(Command.CMD_NAMING, self.players[curuid2].uid + " " + self.players[curuid2].name, 0, 0)		
		
		print "All Players are ready, game started..."
		self.game_started = True
		self.send_all_player(Command.CMD_START, "Game started.")
		print "Send All Players Bet request..."
		self.send_all_player(Command.CMD_BET_QUERY, "Please bet...")

	def on_player_bet(self) :
		if not all([p.bet for p in self.players.values()]):
			return
		
		print "Send All Players first deck..."
		self._send_init_card()
		print "Send All Players second deck..."
		self._send_init_card()
		self._query_add()

	def on_close(self, player) :
		self._del_player(player)	
		if len(self.players) == 1 : # the dealer remainst
			print "All players left." 
			self._initialize()
		else :
			self.send_all_player(Command.CMD_MSG, "Player " + str(player.name) + " left.")
		pass

		
	def on_player_add_card(self) :
		if not all([p.added for p in self.players.values()]):
			return

		if self.dealer.total_point() < 17 :
			self.send_all_player(Command.CMD_MSG, "Dealer point is less than 17.")
			card = self.dealer.acquire_random_card()
			self.send_all_player(Command.CMD_MSG, "Player Dealer got " + str(card) + ".")

		if self.dealer.is_busted() : 
			self.send_all_player(Command.CMD_MSG, "Dealer busted!", 0,0)
			for curuid in self.players :
				if self.players[curuid] is self.dealer:
					continue
				if not self.players[curuid].is_busted() :
					self.players[curuid].send(Command.CMD_RESULT, "You won", self.players[curuid].bet, 0)	
				else :
					self.players[curuid].send(Command.CMD_RESULT, "You lost", self.players[curuid].bet, 0)	
		else :
			_, maxuid = max([(p.total_point(), uid) for (uid, p) in self.players.items()])

			for curuid in self.players :
				if curuid == maxuid and (not self.players[curuid].is_busted()) :
					self.players[curuid].send(Command.CMD_RESULT, "You won", self.players[curuid].bet, 0)	
				else :
					self.players[curuid].send(Command.CMD_RESULT, "You lost", self.players[curuid].bet, 0)
		
		print "Game over."
		self._initialize()

	def add_player(self, player) : 
		assert not self.game_started, 'Game has started.'
		assert player.uid not in self.players, "Player exists."
		self.players[player.uid] = player
		

	def _del_player(self, player) : 
		if player.uid in self.players : 
			self.players.pop(player.uid)

	def _query_add(self) :
		self.send_all_player(Command.CMD_ADD_QUERY, "One more card? [1=yes]")		

	def _send_init_card(self) :		
		for curuid in self.players :
			card = get_rand_card()
			self.send_all_player(Command.CMD_MSG_GETCARD, curuid, card.card_type, card.card_point)		
			self.players[curuid].add_card(card)				

	def send_all_player(self, cmd, msg = "", arg1 = 0, arg2 = 0) :
		print "Send to all players: " + msg
		for curuid in self.players :
			self.players[curuid].send(cmd, msg, arg1, arg2)	
		

gameroom = GameRoom()
	

class PokerRequestHandler(SocketServer.StreamRequestHandler):
	def handle(self):
		print "Client Connected..."
		player = Player()
		player.register_socket_handler(self.request)
		
		player.send(Command.CMD_UID, player.uid, 0, 0)
		
		try:
			gameroom.add_player(player)
		except AssertionError as msg:
			player.send_msg(msg)
		
		while 1 :
			try:
				data = self.request.recv(packer.size)
			except SocketError as e:
				print '*** Failed to receive message to the player %s.' % player.name
				print e
			
			if data == '' : 
				print 'Connection Closed.'
				gameroom.on_close(player)
				break
			cmd, msg, value, _ = packer.unpack(data)
			print 'Received CMD:', cmd
			
			# command dispatch
			if cmd == Command.CMD_TERMINATE : 
				break;
			
			elif cmd == Command.CMD_NAMING:
				username, password = str(msg).split()
				username = trim_msg(username)
				password = trim_msg(password)
				if auth(username, password) == False :
					print "Player login Failed..."
					player.send(Command.CMD_AUTH_RESULT, "Username / Password Wrong.", 1, 0)
				else :
					player.send(Command.CMD_AUTH_RESULT, "Logged in...", 0, 0)
					print "Player %s is renamed to %s." % (player.name, username)
					player.name = username
				
			elif cmd == Command.CMD_READY: # ready
				print "Player %s is ready." % player.name
				player.ready = True
				gameroom.on_player_ready(player)
			
			elif cmd == Command.CMD_MSG:  # client message
				print msg
			
			elif cmd == Command.CMD_BET_REPLY: # bet reply
				print "Player %s bet %i." % (player.name, value)
				player.bet = value
				gameroom.on_player_bet()
			
			elif cmd == Command.CMD_ADD_REPLY: # add card reply
				if value == 1 :
					card = player.acquire_random_card()
					print "Player %s got %s." % (player.name, card)
				else :
					print "Player %s choose not to add." % player.name
				player.added = True
				gameroom.on_player_add_card()


# Server with thread
class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass


if __name__ == "__main__":
	import sys
	if len(sys.argv) > 1 :
		HOST = sys.argv[1]
	else :
		HOST = "localhost"

	# Create the server, binding to localhost on port 10001
	server = ThreadingTCPServer((HOST, PORT), PokerRequestHandler)
	print "Server Started..."
	server.serve_forever()

